﻿using System;
using System.Configuration;
using innovations.ml.core;
using innovations.ml.core.models;
using innovations.ml.core.solvers;
using innovations.ml.data;
using innovations.util.exts.mathdotnet;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.LinearAlgebra.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace innovations.ml.test
{
    [TestClass]
    public class CoreTestsEx2
    {
        [TestMethod]
        public void InitialCostAndTheta()
        {
            CSVLoader csv = StartUp.LoadFile(ConfigurationManager.AppSettings["Ex2Data1File"].ToString());
            Solver solver = new GradientDescent();
            solver.Model = new LogisticalRegressionModel(csv.X, csv.Y, false);
            solver.Iterations = 1;
            solver.Alpha = 0.01;
            solver.Run();
            Assert.AreEqual(0.693147, Math.Round(solver.Model.J, 6));
            Assert.AreEqual(-0.100000, Math.Round(solver.Model.Theta[0], 6));
            Assert.AreEqual(-12.009217, Math.Round(solver.Model.Theta[1], 6));
            Assert.AreEqual(-11.262842, Math.Round(solver.Model.Theta[2], 6));
        }

        [TestMethod]
        public void L_BFGS()
        {
            CSVLoader csv = StartUp.LoadFile(ConfigurationManager.AppSettings["Ex2Data1File"].ToString());
            Solver solver = new L_BFGS();
            solver.Model = new LogisticalRegressionModel(csv.X, csv.Y, false);
            solver.Iterations = 400;
            solver.Run();
            Assert.AreEqual(0.203498, Math.Round(solver.Model.J, 6));
            Assert.AreEqual(-25.1616, Math.Round(solver.Model.Theta[0], 4)); // This only matches to the 4th decimal place
            Assert.AreEqual(0.206233, Math.Round(solver.Model.Theta[1], 6));
            Assert.AreEqual(0.20147, Math.Round(solver.Model.Theta[2], 5)); // This only matches to the 5th decimal place
            double z = (solver.Model.Theta).PointwiseMultiply(new DenseVector(new double[] { 1, 45, 85 })).Sum();
            double probability = Sigmoid.Compute(z);
            Assert.AreEqual(0.77630, Math.Round(probability, 4));
            Vector<double> Predictions = Prediction.Predict(solver.Model.X, solver.Model.Theta);
            double trainingAccuracy = Prediction.ComputeTrainingAccuracy(solver.Model.Y);
            Assert.AreEqual(89.0, trainingAccuracy);
        }

        [TestMethod]
        public void RegularizedLogisticRegression()
        {
            CSVLoader csv = StartUp.LoadFile(ConfigurationManager.AppSettings["Ex2Data2File"].ToString());

            Solver solver = new GradientDescent();
            solver.Model = new LogisticalRegressionModel(csv.X, csv.Y, false);
            solver.Model.Lambda = 1;
            solver.Iterations = 1;

            Vector<double> x1 = new DenseVector(solver.Model.X.RowCount);
            Vector<double> x2 = new DenseVector(solver.Model.X.RowCount);
            solver.Model.X.Column(1, x1);
            solver.Model.X.Column(2, x2);
            Matrix<double> m = MapFeatures(x1, x2);
            solver.Run();
            Assert.AreEqual(0.693147, Math.Round(solver.Model.J, 6));
        }

        [TestMethod]
        public void RegularizationAndAccuracies()
        {
            CSVLoader csv = StartUp.LoadFile(ConfigurationManager.AppSettings["Ex2Data2File"].ToString());
            Solver solver = new L_BFGS();
            solver.Model = new LogisticalRegressionModel(csv.X, csv.Y, false);
            solver.Model.Lambda = 1;
            solver.Iterations = 400;

            Vector<double> x1 = new DenseVector(solver.Model.X.RowCount);
            Vector<double> x2 = new DenseVector(solver.Model.X.RowCount);
            solver.Model.X.Column(1, x1);
            solver.Model.X.Column(2, x2);
            Matrix<double> m = MapFeatures(x1, x2);
            solver.Run();
            Vector<double> predictions = Prediction.Predict(solver.Model.X, solver.Model.Theta);
            double trainingAccuracy = Prediction.ComputeTrainingAccuracy(solver.Model.Y);

            // The exercise says this should be 83.050847, but I think this is an error.
            // Error: In the exercise, the theta vector had size equal to m (m = the number
            // of training examples), instead of n + 1 (n = the number of features).
            Assert.AreEqual(64, trainingAccuracy); 
        }

        /// <summary>
        /// Maps the two input features to quadratic features used in the regularization exercise.
        /// </summary>
        /// <param name="x1">First feature to regularize</param>
        /// <param name="x2">Second feature to regularize</param>
        /// <returns>Returns a new feature array with more features, comprising of X1, X2, X1.^2, X2.^2, X1*X2, X1*X2.^2, etc..</returns>
        private Matrix<double> MapFeatures(Vector<double> x1, Vector<double> x2)
        {
            int degree = 6;
            Matrix<double> m = new DenseMatrix(x1.Count, 1, 1.0);
            for (int i = 1; i <= degree; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    Vector<double> v = x1.PointwisePow(i-j).PointwiseMultiply(x2.PointwisePow(j));
                    m = m.InsertColumn(m.ColumnCount, v);
                }
            }
            return m;
        }
    }
}